package com.dayu.opcua;

import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;

import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;

import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import org.eclipse.milo.opcua.stack.client.UaTcpStackClient;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.dayu.finecomm.protocol.pojo.TPacket;
import com.dayu.finecomm.protocol.pojo.TSensor;
import com.dayu.unitalk.ua.server.KeyStoreLoader;

public class UnitalkUaClient {

	private static final int NAMESPACE_INDEX = 2;

	private final Logger logger = LoggerFactory.getLogger(UnitalkUaClient.class);

	private final KeyStoreLoader loader = new KeyStoreLoader();

	private static final String UNITALK_ENDPOINT = "opc.tcp://127.0.0.1:12686/unitalk";

	private OpcUaClient client = null;

	public UnitalkUaClient() throws Exception {

		this(UNITALK_ENDPOINT);
	}

	public UnitalkUaClient(String urn) throws Exception {

		SecurityPolicy securityPolicy = SecurityPolicy.None;

		EndpointDescription[] endpoints = UaTcpStackClient.getEndpoints(urn).get();

		EndpointDescription endpoint = Arrays.stream(endpoints)
				.filter(e -> e.getSecurityPolicyUri().equals(securityPolicy.getSecurityPolicyUri())).findFirst()
				.orElseThrow(() -> new Exception("no desired endpoints returned"));

		logger.info("Using endpoint: {} [{}]", endpoint.getEndpointUrl(), securityPolicy);

		loader.load();

		OpcUaClientConfig config = OpcUaClientConfig.builder()
				.setApplicationName(LocalizedText.english("Unitalk UA client"))
				.setApplicationUri("urn:hddznet:unitalk:ua-client").setCertificate(loader.getClientCertificate())
				.setKeyPair(loader.getClientKeyPair()).setEndpoint(endpoint)
				.setIdentityProvider(new AnonymousProvider()).setRequestTimeout(uint(60000)).build();

		this.client = new OpcUaClient(config);
	}

	public void writes(TPacket... packets) {

		for (TPacket packet : packets) {

			try {
				write(packet);
			} catch (Exception e) {
				logger.warn("exception on {} with error {}", packet.sn, e);
			}
		}
	}

	/**
	 * The delta in 100 nanosecond intervals between Java epoch (January 1,
	 * 1970) and UTC epoch (Jan 1, 1601).
	 */
	private static final long EPOCH_DELTA = 116444736000000000L;

	public static long utc(long javaTime) {
		return (javaTime * 10000L) + EPOCH_DELTA;
	}

	public static long gmt(long utcTime) {
		return (utcTime - EPOCH_DELTA) / 10000L;
	}

	public void write(TPacket packet) throws Exception {

		for (TSensor sensor : packet.sensors) {

			Variant v = new Variant(sensor.val);

			String id = sensor.sn != null ? sensor.sn : packet.id() + "_" + sensor.channel;

			DateTime timestamp = null;

			if (sensor.timestamp != -1) {
				timestamp = new DateTime(utc(sensor.timestamp));
			} else if (packet.timestamp != null) {
				timestamp = new DateTime(utc(packet.timestamp));
			}

			DataValue value = new DataValue(new Variant(sensor.val), StatusCode.GOOD, timestamp,
					new DateTime(utc(System.currentTimeMillis())));
			CompletableFuture<StatusCode> f = getOpcUaClient().writeValue(new NodeId(NAMESPACE_INDEX, id), value);

			f.whenCompleteAsync(new BiConsumer<StatusCode, Throwable>() {

				@Override
				public void accept(StatusCode t, Throwable u) {
					if (!t.isGood()) {
						logger.info("Unable wrote '{}' to nodeId={} because of {}", v, id, t);
					}
				}
			});
		}
	}

	public OpcUaClient getOpcUaClient() {
		return client;
	}
}
